/**
 * 
 */
package Dungeon;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.NoSuchElementException;

import squarePackage.RockSquare;
import squarePackage.SquareImp;
import be.kuleuven.cs.som.annotate.Basic;
import Enumerations.Directions;
import Exceptions.IllegalDimensionException;

/**
 * A class of shafts
 * 
 * @invar	all elements in the shaft have to be valid elements and have valid coordinates
 * 			|...
 * 
 * @author Pieter
 *
 */
public class Shaft<E extends SquareImp > extends Dungeon<E>  implements Iterable<E> {
	
	/**
	 * A constructor to initialize a new shaft, with 2 dimensions that are fixed, if there is only 1 negative fixed value, this value will
	 * 		be the one that is variable in this shaft
	 * 
	 * @param	x
	 * 			the maximum x dimension value
	 * @param 	y
	 * 			the maximum y dimension value
	 * @param 	z
	 * 			the maximum z dimension value
	 * @param 	fixedX
	 * 			the fixedX value you desire
	 * @param	fixedY
	 * 			the fixedY value you desire
	 * @param 	fixedZ
	 * 			the fixedZ value you desire
	 * @effect	...
	 * 			|super(x,y,z)
	 * @effect	...
	 * 			|this.setFixedValues
	 */
	public Shaft(long x, long y, long z, long fixedX, long fixedY, long fixedZ) throws IllegalDimensionException{
		super(x, y, z);
		if((assertFixedCoordinates(fixedX, fixedY, fixedZ) != 2))
			throw new IllegalArgumentException();
		setFixedValues(fixedX, fixedY, fixedZ);
	}

	
	
	/**
	 * A method to set the values for the fixed dimensions
	 * 
	 * @param 	fixedX
	 * 			the fixedX value you want to initialize
	 * @param 	fixedY
	 * 			the fixedY value you want to initialize
	 * @param	fixedZ
	 * 			the fixedZ value you want to initialize
	 * @post	...
	 * 			new.getFixedX() = fixedX
	 * @post	...
	 * 			new.getFixedY() = fixedY
	 * @post	...
	 * 			new.getFixedZ() = fixedZ
	 * @throws	IllegalArgumentException
	 * 			if there aren't 2 values larger or equal to 0
	 * 			|...
	 * @throws	IllegalDimensionException
	 * 			if any of the values for the fixed values are not valid if they are larger or equal to zero
	 * 			|...
	 */
	private void setFixedValues(long fixedX, long fixedY, long fixedZ) throws IllegalArgumentException, IllegalDimensionException {
		if((assertFixedCoordinates(fixedX, fixedY, fixedZ) != 2))
			throw new IllegalArgumentException();
		this.setFixedX(fixedX);
		this.setFixedY(fixedY);
		this.setFixedZ(fixedZ);
	}
	
	/**
	 * A method to check how many of the given arguments are larger or equal to zero
	 * @param 	fixedX
	 * 			the x value you want to check
	 * @param 	fixedY
	 * 			the y value you want to check
	 * @param 	fixedZ
	 * 			the z value you want to check
	 * @return	the number of arguments that are larger or equal to zero
	 * 			|...
	 */
	private int assertFixedCoordinates(long fixedX, long fixedY, long fixedZ){
		ArrayList<Long> list = new ArrayList<Long>();
		list.add(fixedX); 
		list.add(fixedY);
		list.add(fixedZ);
		int i  = 0 ;
		for(int j = 0; j < list.size() ; j++){
			if(list.get(j) >=0)
				i++;
		}
		return i;
	}
	
	/**
	 * A variable to store the fixed X dimension value, if any
	 */
	private long fixedX;
	
	/**
	 * A variable to store the fixed Y dimension value, if any
	 */
	private long fixedY;
	
	/**
	 * A variable to store the fixed Z dimension value, if any
	 */
	private long fixedZ;

	/**
	 * Returns the fixedX
	 */
	@Basic
	public long getFixedX() {
		return fixedX;
	}

	/**
	 * Returns the fixedY
	 */
	@Basic
	public long getFixedY() {
		return fixedY;
	}

	/**
	 * Returns the fixedZ
	 */
	@Basic
	public long getFixedZ() {
		return fixedZ;
	}

	/**
	 * @param 	fixedX 
	 * 			the fixedX to set
	 * @post	...
	 * 			|new.getFixedZ() = fixedZ
	 * @throws 	IllegalDimensionException 
	 * 			If the parameter is larger or equal to zero, but not a valid dimension
	 * 			|...
	 */
	private void setFixedX(long fixedX) throws IllegalDimensionException {
		if(fixedX >= 0)
			if(!isValidDimension(fixedX, getX()))
				throw new IllegalDimensionException();
		this.fixedX = fixedX;
	}

	/**
	 * @param 	fixedY 
	 * 			the fixedY to set
	 * @post	...
	 * 			|new.getFixedY() = fixedY
	 * @throws 	IllegalDimensionException 
	 * 			If the parameter is larger or equal to zero, but not a valid dimension
	 * 			|...
	 */
	private void setFixedY(long fixedY) throws IllegalDimensionException {
		if(fixedY>=0)
			if(!isValidDimension(fixedY, getY()))
				throw new IllegalDimensionException();
		this.fixedY = fixedY;
	}

	/**
	 * @param 	fixedZ 
	 * 			the fixedZ to set
	 * @post	...
	 * 			new.getFixedZ() = fixedZ
	 * @throws 	IllegalDimensionException 
	 * 			If the parameter is larger or equal to zero, but not a valid dimension
	 * 			|..
	 */
	private void setFixedZ(long fixedZ) throws IllegalDimensionException  {
		if(fixedZ>=0)
			if(!isValidDimension(fixedZ, getZ()))
				throw new IllegalDimensionException();
		this.fixedZ = fixedZ;
	}
	
	/**
	 * A method to check whether a desired coordinate is a valid one for this dungeon
	 * 
	 * @param	x
	 * 			the x coordinate for the element
	 * @param	y 
	 * 			the y coordinate for the element
	 * @param	z 
	 * 			the z coordinate for the element
	 * @return	the coordinate has to be good for the superclass, and also 2 of the dimensions have to be equal to 
	 * 			their corresponding fixed dimension values
	 * 			|...
	 * @return	if there aren't  2  paramaters equal to their corresponding fixed dimension values, result is false
	 * 			|...
	 */
	@Override
	public boolean isValidCoordinate(long x, long y, long z){
		if(super.isValidCoordinate(x, y, z) ){
			if(this.getFixedX()==x  && this.getFixedY() == y){
				return true;
			}
			else if(this.getFixedX()==x  && this.getFixedZ() == z){
				return true;
			}
			else if(this.getFixedY()==y  && this.getFixedZ() == z){
				return true;
			}
		}
		return false;
	}
	
	
	/**
	 * A checker for to see if this dungeon can have this element as an element
	 * 
	 * @return	...
	 * 			|result == super.canHaveAsSquare(element) && !(element instanceof RockSquare))
	 */
	@Override
	public boolean canHaveAsSquare(E element){
		return (super.canHaveAsSquare(element) && !(element instanceof RockSquare));
	}
	
	/**
	 * A method to see whether the square you are adding is ok with the conditions that there cant be walls with doors between elements in the shaft
	 * 
	 * @param 	element
	 * 			the element you are adding
	 * @param 	x
	 * 			the x coordinate where you want to add
	 * @param 	y
	 * 			the y coordinate where you want to add 
	 * @param 	z
	 * 			the z coordinate where you want to add
	 * @return	true if if this element is added, and the borders are adjusted, that there wont be any neigboors with walls with doors inbetween
	 * 			|...
	 */
	public boolean noWallsWithDoorsAllowedBetweenSquares(E element, long x, long y, long z){
		for(int i = 0 ; i < 6 ; i ++ ){
			if(this.hasNeighbourAt(Directions.getDirectionByOrdinal(i), x, y, z)){
				if(preferencedWall(element, this.getSquareAtDirection(Directions.getDirectionByOrdinal(i), x, y, z), Directions.getDirectionByOrdinal(i), Directions.getOppositeDirection(Directions.getDirectionByOrdinal(i))) != null){
					if(preferencedWall(element, this.getSquareAtDirection(Directions.getDirectionByOrdinal(i), x, y, z), Directions.getDirectionByOrdinal(i), Directions.getOppositeDirection(Directions.getDirectionByOrdinal(i))).hasDoor()){
						return false;
					}
				}
			}
		}
		return true;
	}
	
	/**
	 * A method to see whether this element can be added at that coordinate
	 * 
	 * @param 	x
	 * 			the x coordinate where you want to add
	 * @param 	y
	 * 			the y coordinate where you want to add 
	 * @param 	z
	 * 			the z coordinate where you want to add
	 * @param 	element
	 * 			the element you are adding
	 * @return	...
	 * 			|super.canHaveAsSquareAtCoordinate(x, y, z, element) && noWallsWithDoorsAllowedBetweenSquares(element, x, y, z)
	 */
	@Override
	public boolean canHaveAsSquareAtCoordinate(long x, long y, long z, E element){
		return super.canHaveAsSquareAtCoordinate(x, y, z, element) && noWallsWithDoorsAllowedBetweenSquares(element, x, y, z);
	}
	
	/**
	 * A method to see whether a dungeon can be a root of another dungeon
	 * 
	 * @param 	dungeon
	 * 			the dungeon you want to check whether it can be a root
	 * @return	...
	 * 			|result == (dungeon == this && ! dungeon.isTerminated())
	 */
	@Override
	public  boolean canBeRoot(Dungeon<E> dungeon){
		return (dungeon == this && !dungeon.isTerminated());
	}
	
	/**
	 * Returns an iterator that returns all the squares in the shaft
	 */
	@Override
	public Iterator<E> iterator() {
			return new Iterator<E>() {

				/**
				 * An iterator over the squares
				 */
				private Iterator<E> shaftIterator = getCoordinatesAndSquaresInDungeon().values().iterator();
				
				/**
				 * Returns true if the iteration has more elements
				 * 
				 * @return	true if the iterator has more elements.
				 * 			|result == shaftIterator.hasNext())
				 */
				@Override
				public boolean hasNext() {
					return shaftIterator.hasNext();
				}

				/**
				 * Returns the next element in the iteration. 
				 * 
				 * @return	the next element in the iteration. 
				 * 			|result == shaftIterator.next()
				 * @throws	NoSuchElementException()
				 * 			...
				 * 			|if(!hasNext())
				 */
				@Override
				public E next() throws NoSuchElementException {
					if(!hasNext()){
						throw new NoSuchElementException();
					}
					else{
						return shaftIterator.next();
					}
				}
				
				/**
				 * Removes the last element returned by the iterator
				 * 
				 * @throws 	UnsupportedOperationException
				 */
				@Override
				public void remove() throws UnsupportedOperationException {
					throw new UnsupportedOperationException();
				}
			};
	}		

	

}
